using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Receiving;
using MQTTnet.Client.Subscribing;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace Infrastructure.MQ
{
    /// <summary>
    /// MQTT协议客户端
    /// </summary>
    public class MQTT
    {
        private readonly IMqttClient MqttClient = new MqttFactory().CreateMqttClient();
        private readonly IMqttClientOptions Options;

        #region Ctor(s)

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="host">主机地址</param>
        /// <param name="port">端口,1-65535,MQTT协议默认端口一般为1883</param>
        /// <param name="user">用户名,可为空,如需设置,user和password必须同时设置</param>
        /// <param name="password">密码,可为空,如需设置,user和password必须同时设置</param>
        /// <param name="clientId">客户端Id,可为空,建议设置</param>
        /// <param name="timeout">通信超时秒数</param>
        public MQTT(string host, int port, string user, string password, string clientId, int timeout = 10)
        {
            Options = GetBuilder(host, port, user, password, clientId, timeout)
                .Build();
        }

        /// <summary>
        /// 构造函数(使用TLS协议)
        /// </summary>
        /// <param name="host">主机地址</param>
        /// <param name="port">端口,1-65535,MQTT协议默认端口一般为1883</param>
        /// <param name="user">用户名,可为空,如需设置,user和password必须同时设置</param>
        /// <param name="password">密码,可为空,如需设置,user和password必须同时设置</param>
        /// <param name="clientId">客户端Id,可为空,建议设置</param>
        /// <param name="timeout">通信超时秒数</param>
        /// <param name="certPath">证书存放位置,绝对路径带后缀名</param>
        public MQTT(string host, int port, string user, string password, string clientId, string certPath,
            int timeout = 10)
        {
            // 未指定证书路径
            if (string.IsNullOrEmpty(certPath))
                throw new ArgumentNullException(certPath);

            // 从文件创建证书
            var cert = X509Certificate.CreateFromSignedFile(certPath);

            // tls参数
            MqttClientOptionsBuilderTlsParameters tls = new MqttClientOptionsBuilderTlsParameters();
            tls.AllowUntrustedCertificates = true;
            tls.UseTls = true;
            tls.SslProtocol = SslProtocols.Tls12;
            tls.CertificateValidationCallback = delegate { return true; };
            tls.IgnoreCertificateChainErrors = true;
            tls.IgnoreCertificateRevocationErrors = true;
            tls.Certificates = new List<X509Certificate> { cert };

            Options = GetBuilder(host, port, user, password, clientId, timeout)
                .WithTls(tls)
                .Build();
        }

        #endregion

        /// <summary>
        /// 启动与服务端的连接,并注册Connected、Disconnected、MessageReceived事件
        /// </summary>
        /// <param name="connected">连接事件</param>
        /// <param name="disconnected">断开连接事件</param>
        /// <param name="messageReceived">消息接收事件</param>
        /// <returns></returns>
        public Task<MqttClientAuthenticateResult> StartAsync(Func<MqttClientConnectedEventArgs, Task> connected,
           Func<MqttClientDisconnectedEventArgs, Task> disconnected,
          Action<MqttApplicationMessageReceivedEventArgs> messageReceived)
        {
            if (connected != null)
            {
                MqttClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(
                    new Func<MqttClientConnectedEventArgs, Task>(connected));
            }

            if (disconnected != null)
            {
                MqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(
                    new Func<MqttClientDisconnectedEventArgs, Task>(disconnected));
            }

            if (messageReceived != null)
            {
                MqttClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(
                    new Action<MqttApplicationMessageReceivedEventArgs>(messageReceived));
            }

            return MqttClient.ConnectAsync(Options);
        }

        /// <summary>
        /// 启动与服务端的连接,并注册Connected、Disconnected、MessageReceived事件
        /// </summary>
        /// <param name="connected">连接事件</param>
        /// <param name="disconnected">断开连接事件</param>
        /// <param name="messageReceived">消息接收事件</param>
        /// <returns></returns>
        public Task<MqttClientAuthenticateResult> StartAsync(Func<MqttClientConnectedEventArgs, Task> connected,
           Func<MqttClientDisconnectedEventArgs, Task> disconnected,
          Func<MqttApplicationMessageReceivedEventArgs, Task> messageReceived)
        {
            if (connected != null)
            {
                MqttClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(
                    new Func<MqttClientConnectedEventArgs, Task>(connected));
            }

            if (disconnected != null)
            {
                MqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(
                    new Func<MqttClientDisconnectedEventArgs, Task>(disconnected));
            }

            if (messageReceived != null)
            {
                MqttClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(
                    new Func<MqttApplicationMessageReceivedEventArgs, Task>(messageReceived));
            }

            return MqttClient.ConnectAsync(Options);
        }

        /// <summary>
        /// 订阅单个主题
        /// </summary>
        /// <param name="topicFilter">订阅的主题</param>
        public Task<MqttClientSubscribeResult> SubscribeAsync(TopicFilter topicFilter)
        {
            return SubscribeAsync(new List<TopicFilter>() { topicFilter });
        }

        /// <summary>
        /// 订阅多个主题
        /// </summary>
        /// <param name="topicFilters">订阅的主题列表</param>
        public Task<MqttClientSubscribeResult> SubscribeAsync(List<TopicFilter> topicFilters)
        {
            if (topicFilters != null)
            {
                // 主题为空的filter不进行订阅
                var filters = topicFilters
                    .Where(p => !string.IsNullOrEmpty(p.Topic))
                    .ToArray();

                if (filters.Length > 0)
                    return MqttClient.SubscribeAsync(filters);
            }

            return Task.FromResult(new MqttClientSubscribeResult());
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <returns></returns>
        public Task Disconnected()
        {
            return MqttClient.DisconnectAsync();
        }

        /// <summary>       
        /// 发布消息。
        /// <paramref name="qos"/>
        /// <para>0 - 最多一次</para>
        /// <para>1 - 至少一次</para>
        /// <para>2 - 仅一次</para>
        /// </summary>
        /// <param name="topic">主题名称</param>
        /// <param name="message">消息内容</param>
        /// <param name="retained">是否保持消息</param>
        /// <param name="qos">服务质量</param>
        /// <returns></returns>
        public Task<MqttClientPublishResult> Publish(string topic, string message,
            MqttQualityOfServiceLevel qos, bool retained)
        {
            // 发布主题为空
            if (string.IsNullOrEmpty(topic))
                return Task.FromResult(new MqttClientPublishResult()
                {
                    ReasonCode = MqttClientPublishReasonCode.TopicNameInvalid,
                    ReasonString = "topic is null. "
                });

            // 未连接,重连
            if (!MqttClient.IsConnected)
                MqttClient.ConnectAsync(Options);

            // 重连失败
            if (!MqttClient.IsConnected)
            {
                return Task.FromResult(new MqttClientPublishResult()
                {
                    ReasonCode = MqttClientPublishReasonCode.UnspecifiedError,
                    ReasonString = "Disconnected. "
                });
            }

            // 准备消息
            MqttApplicationMessageBuilder messageBuilder = new MqttApplicationMessageBuilder()
                .WithTopic(topic)
                .WithPayload(message)
                .WithRetainFlag(retained)
                .WithQualityOfServiceLevel(qos);

            // 发布消息
            return MqttClient.PublishAsync(messageBuilder.Build());
        }

        #region Private method

        /// <summary>
        /// 获取参数设置的构造器
        /// </summary>
        /// <param name="host">主机地址</param>
        /// <param name="port">端口,1-65535,MQTT协议默认端口一般为1883</param>
        /// <param name="user">用户名,可为空,如需设置,user和password必须同时设置</param>
        /// <param name="password">密码,可为空,如需设置,user和password必须同时设置</param>
        /// <param name="clientId">客户端Id,可为空,建议设置</param>
        /// <param name="timeout">通信超时秒数</param>
        /// <returns>MqttClientOptionsBuilder</returns>
        private MqttClientOptionsBuilder GetBuilder(string host, int port, string user, string password, string clientId,
            int timeout)
        {
            if (string.IsNullOrEmpty(host))
                throw new ArgumentNullException("host");

            if (port < 1 || port > 65535)
                throw new ArgumentException("port must between 1 and 65535.");

            if (timeout < 0)
                throw new ArgumentException("timeout must greater than 0.");

            var builder = new MqttClientOptionsBuilder()
                .WithTcpServer(host, port)
                .WithClientId(clientId)
                .WithCommunicationTimeout(TimeSpan.FromSeconds(timeout))
                .WithCleanSession(false)
                .WithKeepAlivePeriod(TimeSpan.FromSeconds(20))
                .WithKeepAliveSendInterval(TimeSpan.FromSeconds(10));

            if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password))
                builder.WithCredentials(user, password);

            return builder;
        }

        #endregion

    }
}
